/*
Hermes Core: General Purpose Mobile Agent Platform
Copyright (C) 2006 UNICAM - Università degli Studi di Camerino

Group leaders: Flavio Corradini and Emanuela Merelli

Developers: Ezio Bartocci, Diego Bonura, Rosario Culmone,
Davide Lorenzoli, Leonardo Mariani, Francesca Piersigilli, 
Lorenzo Scortichini, Leonardo Vito.

GNU Lesser Public License

This library is free software; you can redistribute it 
and/or modify it under the terms of the GNU Lesser General 
Public License as published by the Free Software Foundation, 
version 2.1 of the License.

This library is distributed in the hope that it will be useful, 
but WITHOUT ANY WARRANTY; without even the implied warranty of 
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 
See the GNU Library General Public License for more details.

You should have received a copy of the GNU Library General Public 
License along with this library; if not, write to the
Free Software Foundation, Inc., 59 Temple Place - Suite 330,
Boston, MA 02111-1307, USA.

Emanuela Merelli
Dipartimento di Matematica ed Informatica
Università di Camerino
via Madonna delle Carceri, 62032 Camerino, Italy
E.Mail: emanuela.merelli@unicam.it
*/
package hermesV2.core.net;
import hermesV2.CurrentAgentLocation;
import hermesV2.Envelope;
import hermesV2.PlaceAddress;
import hermesV2.PlaceDescriptor;
import hermesV2.agent.UserAgent;
import hermesV2.agent.Identificator;
import hermesV2.core.id.Trace;
import hermesV2.core.net.SendReceive;
import hermesV2.core.starter.Starter;
import hermesV2.util.HermesLogger;
import hermesV2.util.VectorQueue;
import hermesV2.Place;
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.net.Socket;
import java.util.Properties;
import java.util.StringTokenizer;

import android.util.Log;
/**
 * Makes possible to handle inputs, it receives a message for the remote place
 * and it tries to manage it.
 * 
 * @author Diego Bonura
 */
public class InputRequest implements Runnable {
	/** The socket of the current connection. */
	private Socket socket;
	/**
	 * The istance of the <code>SendReceive</code> component, it needs this
	 * istance when a new class file is request to resolve a new agent entering.
	 */
	private SendReceive sendReceive;
	/**
	 * The stream used to the message enter, it uses the
	 * <code>SmartClassLoader</code>.
	 */
	private SmartObjectInputStream smartInputEnv;
	/**
	 * The stream used to the agent enter, it uses the
	 * <code>SmartClassLoader</code>.
	 */
	private SmartObjectInputStream smartInputAgent;
	/** The custom class loader. */
	private SmartClassLoader smartLoader;
	/** The object output stream. */
	private ObjectOutputStream outputEnv;
	/** Interface of the <code>AgentStarter</code> component. */
	private Starter starter;
	/** DOCUMENT ME! */
	private Trace trace;
	/** The object received. */
	private Object obj;
	/** The queue that contains envelopes for the basiServicesLayer */
	private VectorQueue<Envelope<?>> envelopeQueue;
	/** The queue that contains placeDescriptor for the basiServicesLayer */
	//private VectorQueue<PlaceDescriptor>  placeDescriptorQueue;
	/** The current place properties */
	private Properties properties;
	/** The address of the remote place that made the current connection. */
	private PlaceAddress placeAddress;
	/**
	 * true if the agent migrated to the current place is resolved and
	 * everithing is ok otherwise false.
	 */
	private boolean agentEnterResult;
	// -------------------------------------------------------------------------------
	/**
	 * Creates a new <code>InputRequest</code> object.
	 * 
	 * @param socket
	 *            The current socket on with the remote place.
	 * @param placeAddress
	 *            The remote place address.
	 * @param sendReceive
	 *            The <code>SendReceive</code> component.
	 * @param starter
	 *            The interface of the <code>SendReceive</code> component.
	 * @param trace
	 *            The interface of the <code>Id</code> component.
	 * @param properties
	 *            The current place properties.
	 * @param envelopeQueue
	 *            The queue to put envelope arrived to the current platform.
     * @param placeDescriptorQueue
	 *            The queue to put placeDescriptor arrived to the current platform.
	 * 
	 *  
	 */
	public InputRequest(Socket socket, PlaceAddress placeAddress,
			SendReceive sendReceive, Starter starter, Trace trace,
			VectorQueue<Envelope<?>> envelopeQueue,VectorQueue<PlaceDescriptor>  placeDescriptorQueue, Properties properties) {
		this.socket = socket;
		this.starter = starter;
		this.trace = trace;
		this.sendReceive = sendReceive;
		this.envelopeQueue = envelopeQueue;
		//this.placeDescriptorQueue = placeDescriptorQueue;
		this.properties = properties;
		this.placeAddress = placeAddress;
		new Thread(this).start();
	}
	// ------------------------------------------------------------------------------------
	/**
	 * When the thread starts the <code>InputRequest</code> reads new messages
	 * from the <code>socket</code> handles them.
	 */
	public void run() {
		try {
			this.smartLoader = new SmartClassLoader(properties
					.getProperty("HERMES.STORE_DIRECTORY"), "class", properties
					.getProperty("HERMES.SAVE_AGENTS").equals("true"),
					sendReceive);
			this.smartInputEnv = new SmartObjectInputStream(
					new BufferedInputStream(socket.getInputStream()));
			this.outputEnv = new ObjectOutputStream(socket.getOutputStream());
			smartInputEnv.setLoader(smartLoader);
			obj = this.smartInputEnv.readObject(); 
			smartLoader.SetPlaceAddress(((Envelope <?>) obj)
					.getSenderPlaceAddress());
			if (obj.getClass().getName().equals("hermesV2.Envelope")) {
				switch (((Envelope<?>) obj).getType()) {
					case Envelope.CLASS_REQUEST :
						String classFileName = (String)((Envelope<?>) obj)
								.getObject();
						String UID = classFileName.substring(classFileName
								.lastIndexOf(".") + 1);
						Log.i("hermes", "Message class request "+ classFileName+ " serialUID "+ UID+ "From place:"+ ((Envelope<?>) obj).getSenderPlaceAddress());
						HermesLogger.log(HermesLogger.DEBUG,
								"Message class request "
										+ classFileName
										+ " serialUID "
										+ UID
										+ "From place:"
										+ ((Envelope<?>) obj)
												.getSenderPlaceAddress());
						String className = classFileName.substring(0,
								classFileName.lastIndexOf("."));
						byte[] resultClassRequest = null;
						File classFile = null;
						/* ricerco nella cache di sistema con l'UID */
						resultClassRequest = smartLoader
								.getCachedClassWithSerialVersion(UID);
						if (resultClassRequest == null) {
							/* Ricerco nel file system la classe con l'UID */
							classFile = findFile(classFileName, false);
							if (classFile == null) {
								classFile = findFile((String) ((Envelope<?>) obj)
										.getObject(), true);
							}
							if (classFile == null) {
								/*
								 * Ricerco nel file system la classe con solo il
								 * nome sperando che sia la stessa versione
								 */
								classFile = findFile(className, false);
								if (classFile == null) {
									classFile = findFile(className, true);
								}
							}
							if (classFile != null) {
								FileInputStream fileInputStream = new FileInputStream(
										classFile);
								byte[] classByte = new byte[fileInputStream
										.available()];
								fileInputStream.read(classByte);
								fileInputStream.close();
								this.outputEnv.writeObject(new Envelope<byte []>(
										Envelope.CLASS_ENTER, classByte,
										placeAddress, ((Envelope<?>) obj)
												.getSenderPlaceAddress()));
							} else {
								this.outputEnv.writeObject(new Envelope<Object>(
										Envelope.CLASS_NOTFOUND, null,
										placeAddress, ((Envelope<?>) obj)
												.getSenderPlaceAddress()));
							}
						} else {
							this.outputEnv.writeObject(new Envelope<byte []>(
									Envelope.CLASS_ENTER, resultClassRequest,
									placeAddress, ((Envelope<?>) obj)
											.getSenderPlaceAddress()));
						}
						this.outputEnv.flush();
						break;
					case Envelope.AGENT_ENTER :
						HermesLogger.log(HermesLogger.INFO,
								"Agent enter request message received from place: "
										+ ((Envelope<?>) obj)
												.getSenderPlaceAddress());
						Log.i("hermes",
								"Agent enter request message received from place: "
										+ ((Envelope<?>) obj)
												.getSenderPlaceAddress());
						try {
							ByteArrayInputStream byteIStream = new ByteArrayInputStream(
									((Envelope<?>) obj).getSmartPack()
											.getObjectByte());
							this.smartInputAgent = new SmartObjectInputStream(
									byteIStream);
							this.smartInputAgent.setLoader(smartLoader);
							Object a = smartInputAgent.readObject();
							
//							It upgrades the position of the agent in the borns table
			                //First it checks if the update has to be locally
			                //then it tries to update the location in remote
							if (a instanceof hermesV2.agent.UserAgent){
							    
			                if (trace.updateAgentAddress(((UserAgent)a).getIdentificator(),Place.getCurrentPlaceDescriptor().getPlaceAddress() ) == null) {
			                    Envelope<?> reply = sendReceive.sendEnvelope(new Envelope<CurrentAgentLocation>(Envelope.UPDATED_ADDRESS, new CurrentAgentLocation(((UserAgent)a).getIdentificator(), Place.getCurrentPlaceDescriptor().getPlaceAddress()), Place.getCurrentPlaceDescriptor().getPlaceAddress(), ((UserAgent)a).getIdentificator().getBornPlaceAddress()));

			                    if (reply.getType() == Envelope.RECEPTION_OK) {
			                        HermesLogger.log(HermesLogger.DEBUG, "Update address done Agent:" + ((UserAgent)a).getIdentificator());
			                        Log.i("hermes", "Update address done Agent:" + ((UserAgent)a).getIdentificator());
			                    } else {
			                    	Log.i("hermes", "Error on update address Agent:" + ((UserAgent)a).getIdentificator());
			                        HermesLogger.log(HermesLogger.WARNING, "Error on update address Agent:" + ((UserAgent)a).getIdentificator());
			                    }
			                }
			                }
							
							
							agentEnterResult = starter.startAgent(a,
									UserAgent.RESTART, false);
							if (agentEnterResult) {
								outputEnv.writeObject(new Envelope<String>(
										Envelope.RECEPTION_OK, "Migration ok",
										placeAddress, ((Envelope<?>) obj)
												.getSenderPlaceAddress()));
							} else {
								outputEnv.writeObject(new Envelope<String>(
										Envelope.RECEPTION_KO, "Migration ko",
										placeAddress, ((Envelope<?>) obj)
												.getSenderPlaceAddress()));
							}
							this.outputEnv.flush();
							break;
						} catch (Exception e) {
							outputEnv.writeObject(new Envelope<String>(
									Envelope.RECEPTION_KO, "Migration ko",
									placeAddress, ((Envelope<?>) obj)
											.getSenderPlaceAddress()));
							HermesLogger
									.log(
											HermesLogger.WARNING,
											"Migration Ko from"
													+ placeAddress
													+ " to "
													+ ((Envelope<?>) obj)
															.getSenderPlaceAddress(),
											e);
							Log.i("hermes","Migration Ko from"	+ placeAddress+ " to "+ ((Envelope<?>) obj).getSenderPlaceAddress(),e);
						}
					case Envelope.GENERAL_MESSAGE :
						HermesLogger.log(HermesLogger.INFO,
								"General message received from place:"
										+ ((Envelope<?>) obj)
												.getSenderPlaceAddress());
						Log.i("hermes","General message received from place:"+ ((Envelope<?>) obj).getSenderPlaceAddress());
						outputEnv.writeObject(new Envelope<String>(
								Envelope.RECEPTION_OK, "Migration ok",
								placeAddress, ((Envelope<?>) obj)
										.getSenderPlaceAddress()));
						this.outputEnv.flush();
						break;
					case Envelope.UPDATED_ADDRESS :
						HermesLogger.log(HermesLogger.DEBUG,
								"Update messagge received from place:"
										+ ((Envelope<?>) obj)
												.getSenderPlaceAddress()+" AgentName: "+
												((CurrentAgentLocation) ((Envelope<?>) obj)
														.getObject()).getIdentificator().getName());
						Log.i("hermes","Update messagge received from place:"+ ((Envelope<?>) obj).getSenderPlaceAddress()+" AgentName: "+((CurrentAgentLocation) ((Envelope<?>) obj).getObject()).getIdentificator().getName());
						if (trace.updateAgentAddress(
								((CurrentAgentLocation) ((Envelope<?>) obj)
										.getObject()).getIdentificator(),
								((CurrentAgentLocation) ((Envelope<?>) obj)
										.getObject()).getPlaceAddress()) != null) {
							outputEnv.writeObject(new Envelope<String>(
									Envelope.RECEPTION_OK, "Update ok",
									placeAddress, ((Envelope<?>) obj)
											.getSenderPlaceAddress()));
						} else {
							outputEnv.writeObject(new Envelope<String>(
									Envelope.RECEPTION_KO, "Update ko",
									placeAddress, ((Envelope<?>) obj)
											.getSenderPlaceAddress()));
						}
						this.outputEnv.flush();
						break;
					case Envelope.ADDRESS_REQUEST :
						HermesLogger.log(HermesLogger.DEBUG,
								"Agent address request received from place:"
										+ ((Envelope<?>) obj)
												.getSenderPlaceAddress());
						Log.i("hermes","Agent address request received from place:"+ ((Envelope<?>) obj).getSenderPlaceAddress());
						Identificator receiverId = ((Identificator) ((Envelope<?>) obj)
								.getObject());
						PlaceAddress currentAgentPlace = trace
								.getCurrentAddress(receiverId);
						if (currentAgentPlace != null) {
							CurrentAgentLocation currentAgentLocation = new CurrentAgentLocation(
									receiverId, currentAgentPlace);
							outputEnv.writeObject(new Envelope<CurrentAgentLocation>(
									Envelope.ADDRESS_ENTER,
									currentAgentLocation, placeAddress,
									((Envelope<?>) obj).getSenderPlaceAddress()));
						} else {
							//Current place not found!
							outputEnv.writeObject(new Envelope<String>(
									Envelope.RECEPTION_KO,
									"Agent Id not found", placeAddress,
									((Envelope<?>) obj).getSenderPlaceAddress()));
						}
						this.outputEnv.flush();
						break;
					case Envelope.MESSAGE_TO_USERAGENT :
						HermesLogger.log(HermesLogger.INFO,
								"Agent message received from place:"
										+ ((Envelope<?>) obj)
												.getSenderPlaceAddress());
						Log.i("hermes","Agent message received from place:"+ ((Envelope<?>) obj).getSenderPlaceAddress());
						envelopeQueue.append(((Envelope<?>) obj));
						//Devo effettuare dei controlli con il trace...
						//vedere se l'agente �realemente nel place!
						outputEnv.writeObject(new Envelope<String>(
								Envelope.RECEPTION_OK, "Message Reception ok",
								placeAddress, ((Envelope<?>) obj)
										.getSenderPlaceAddress()));
						this.outputEnv.flush();
						break;
					case Envelope.ADDRESS_ERASE_REQUEST :
						HermesLogger.log(HermesLogger.DEBUG,
								"Agent erase request message received from place:"
										+ ((Envelope<?>) obj)
												.getSenderPlaceAddress());
						Log.i("hermes","Agent erase request message received from place:"+ ((Envelope<?>) obj).getSenderPlaceAddress());
						Identificator agentId = (Identificator) (((Envelope<?>) obj)
								.getObject());
						if (trace.eraseAgentAddress(agentId)) {
							outputEnv.writeObject(new Envelope<String>(
									Envelope.RECEPTION_OK,
									"Message Reception ok", placeAddress,
									((Envelope<?>) obj).getSenderPlaceAddress()));
						} else {
							outputEnv.writeObject(new Envelope<String>(
									Envelope.RECEPTION_KO,
									"No agent entry in the Id component",
									placeAddress, ((Envelope<?>) obj)
											.getSenderPlaceAddress()));
						}
						this.outputEnv.flush();
						break;
					default :
						HermesLogger.log(HermesLogger.WARNING,
								"Envelope type unknow from place:"
										+ ((Envelope<?>) obj)
												.getSenderPlaceAddress());
						Log.i("hermes", "Envelope type unknow from place:"+ ((Envelope<?>) obj).getSenderPlaceAddress());
					this.outputEnv.flush();
						break;
				}
			} else {
				HermesLogger.log(HermesLogger.WARNING,
						"The current place receives an unknow class from place:"
								+ ((Envelope<?>) obj).getSenderPlaceAddress()
								+ "class: " + obj.getClass().getName());
				Log.i("hermes", "The current place receives an unknow class from place:"
						+ ((Envelope<?>) obj).getSenderPlaceAddress()
						+ "class: " + obj.getClass().getName());
			}
		} catch (Exception e) {
			HermesLogger.log(HermesLogger.WARNING, "Exception", e);
			Log.i("hermes",e.getStackTrace()[0].toString());
		} finally {
			try {
				if (smartInputEnv != null) {
					smartInputEnv.close();
				}
				if (outputEnv != null) {
				    outputEnv.close();
				}
				if (socket != null) {
					socket.close();
				}
			} catch (IOException e) {
				HermesLogger.log(HermesLogger.WARNING, "IOException", e);
				Log.i("hermes","IOException"+e);
			}
		}
	}
	/**
	 * When a remote place asks for a class file it searches the file on the
	 * current file system.
	 * 
	 * @param className
	 *            The name of the class searched by the remote place.
	 * @param resolvePackageName
	 *            true when the className is diveded in directory for example
	 *            packageName.foo -> packageName\foo false when is searched the
	 *            packageName.foo file.
	 * 
	 * @return the file or null.
	 *  
	 */
	private File findFile(String className, boolean resolvePackageName) {
		String dir_classes = properties.getProperty("HERMES.STORE_DIRECTORY");
		String class_path = System.getProperty("java.class.path");
		String file_separator = System.getProperty("file.separator");
		String path_separator = System.getProperty("path.separator");
		if (resolvePackageName) {
			className = className.replace(".".charAt(0), file_separator
					.charAt(0));
		}
		className = className + ".class";
		if (dir_classes != null) {
			class_path += (path_separator + dir_classes);
		}
		HermesLogger.log(HermesLogger.DEBUG, "Searching for " + className
				+ " in " + class_path);
		Log.i("hermes","Searching for " + className
				+ " in " + class_path );
		StringTokenizer st = new StringTokenizer(class_path, path_separator);
		String file_path;
		String dir;
		boolean find_file = false;
		File f = null;
		// find directory containing the requested class
		while (st.hasMoreTokens() && !find_file) {
			dir = st.nextToken();
			if (dir.endsWith(file_separator)) {
				file_path = dir + className;
			} else {
				file_path = dir + file_separator + className;
			}
			f = new File(file_path);
			if (f.exists()) {
				find_file = true;
			}
		}
		if (!find_file) {
			f = null;
		}
		return f;
	}
}